package spinal.tester.scalatest

import org.scalatest.funsuite.AnyFunSuite

import spinal.sim._
import spinal.core._
import spinal.core.sim._
import spinal.lib.com.linecode.Encoding8b10b

class Encoding8b10bTest extends SpinalAnyFunSuite {

  def encodeTestCase(dut: Encoding8b10b.Encoder, data: String, kWord: Boolean, encoded: String,
                     kError: Boolean) {
    dut.io.data #= BigInt(data, 2)
    dut.io.kWord #= kWord
    dut.io.stall #= false
    dut.clockDomain.waitSampling(1)
    dut.io.data #= BigInt("00000000", 2)
    dut.clockDomain.waitSampling(1)
    sleep(1)

    val dataString = dut.io.encoded.toInt.toBinaryString
    val leadingZeros = "0" * (10 - dataString.length)
    println(s"Encoding: ${leadingZeros}${dataString}")
    println(s"Expected: ${encoded}")
    assert(dut.io.encoded.toBigInt == BigInt(encoded, 2))
    assert(dut.io.kError.toBoolean == kError)
  }

  def encodeTestCaseDWord(dut: Encoding8b10b.Encoder, data: String, encoded: String) {
    val dWord = s"(D.${BigInt(data.substring(3, 8), 2)}.${BigInt(data.substring(0, 3), 2)})"
    println(s"Data Word: ${data} ${dWord}")

    encodeTestCase(dut, data, false, encoded, false)
  }

  def encodeTestCaseKWord(dut: Encoding8b10b.Encoder, data: String, encoded: String,
                          kError: Boolean = false) {
    val kWord = s"(K.${BigInt(data.substring(3, 8), 2)}.${BigInt(data.substring(0, 3), 2)})"
    println(s"K Word: ${data} ${kWord}")

    encodeTestCase(dut, data, true, encoded, kError)
  }

  def decodeTestCase(dut: Encoding8b10b.Decoder, data: String, encoded: String, error: Boolean,
                     kWord: Boolean = false) {
    dut.io.encoded #= BigInt(encoded, 2)
    dut.io.stall #= false
    dut.clockDomain.waitSampling(1)
    dut.io.encoded #= BigInt("1011010100", 2)
    sleep(1)

    val dataString = dut.io.data.toInt.toBinaryString
    val leadingZeros = "0" * (8 - dataString.length)
    println(s"Data:     ${leadingZeros}${dataString}")
    println(s"Expected: ${data}")
    if (!error) {
      assert(dut.io.data.toBigInt == BigInt(data, 2))
    }
    assert(dut.io.kWord.toBoolean == kWord, "| kWord mismatch")
    assert(dut.io.codeError.toBoolean == error, "| Code Error mismatch")
  }

  def decodeTestCaseDWord(dut: Encoding8b10b.Decoder, data: String, encoded: String) {
    println(s"Encoded: ${encoded}")

    decodeTestCase(dut, data, encoded, false)
  }

  def decodeTestCaseKWord(dut: Encoding8b10b.Decoder, data: String, encoded: String) {
    println(s"Encoded: ${encoded}")

    decodeTestCase(dut, data, encoded, false, true)
  }

  def decodeTestCaseError(dut: Encoding8b10b.Decoder, data: String, encoded: String,
                          error: Boolean) {
    println(s"Encoded: ${encoded}")
    println(s"Expected error: ${error}")

    decodeTestCase(dut, data, encoded, error)
  }


  val kWords = Map[String, (String, String, Boolean)](
    "00011100" -> ("0011110100", "1100001011", false),
    "00111100" -> ("0011111001", "1100000110", true),
    "01011100" -> ("0011110101", "1100001010", true),
    "01111100" -> ("0011110011", "1100001100", true),
    "10011100" -> ("0011110010", "1100001101", false),
    "10111100" -> ("0011111010", "1100000101", true),
    "11011100" -> ("0011110110", "1100001001", true),
    "11111100" -> ("0011111000", "1100000111", false),
    "11110111" -> ("1110101000", "0001010111", false),
    "11111011" -> ("1101101000", "0010010111", false),
    "11111101" -> ("1011101000", "0100010111", false),
    "11111110" -> ("0111101000", "1000010111", false)
  )

  val dWords = Map[String, (String, String, Boolean)](
      "00000000" -> ("1001110100", "0110001011", false),
      "00000001" -> ("0111010100", "1000101011", false),
      "00000010" -> ("1011010100", "0100101011", false),
      "00000011" -> ("1100011011", "1100010100", true),
      "00000100" -> ("1101010100", "0010101011", false),
      "00000101" -> ("1010011011", "1010010100", true),
      "00000110" -> ("0110011011", "0110010100", true),
      "00000111" -> ("1110001011", "0001110100", true),
      "00001000" -> ("1110010100", "0001101011", false),
      "00001001" -> ("1001011011", "1001010100", true),
      "00001010" -> ("0101011011", "0101010100", true),
      "00001011" -> ("1101001011", "1101000100", true),
      "00001100" -> ("0011011011", "0011010100", true),
      "00001101" -> ("1011001011", "1011000100", true),
      "00001110" -> ("0111001011", "0111000100", true),
      "00001111" -> ("0101110100", "1010001011", false),
      "00010000" -> ("0110110100", "1001001011", false),
      "00010001" -> ("1000111011", "1000110100", true),
      "00010010" -> ("0100111011", "0100110100", true),
      "00010011" -> ("1100101011", "1100100100", true),
      "00010100" -> ("0010111011", "0010110100", true),
      "00010101" -> ("1010101011", "1010100100", true),
      "00010110" -> ("0110101011", "0110100100", true),
      "00010111" -> ("1110100100", "0001011011", false),
      "00011000" -> ("1100110100", "0011001011", false),
      "00011001" -> ("1001101011", "1001100100", true),
      "00011010" -> ("0101101011", "0101100100", true),
      "00011011" -> ("1101100100", "0010011011", false),
      "00011100" -> ("0011101011", "0011100100", true),
      "00011101" -> ("1011100100", "0100011011", false),
      "00011110" -> ("0111100100", "1000011011", false),
      "00011111" -> ("1010110100", "0101001011", false),
      "00100000" -> ("1001111001", "0110001001", true),
      "00100001" -> ("0111011001", "1000101001", true),
      "00100010" -> ("1011011001", "0100101001", true),
      "00100011" -> ("1100011001", "1100011001", false),
      "00100100" -> ("1101011001", "0010101001", true),
      "00100101" -> ("1010011001", "1010011001", false),
      "00100110" -> ("0110011001", "0110011001", false),
      "00100111" -> ("1110001001", "0001111001", false),
      "00101000" -> ("1110011001", "0001101001", true),
      "00101001" -> ("1001011001", "1001011001", false),
      "00101010" -> ("0101011001", "0101011001", false),
      "00101011" -> ("1101001001", "1101001001", false),
      "00101100" -> ("0011011001", "0011011001", false),
      "00101101" -> ("1011001001", "1011001001", false),
      "00101110" -> ("0111001001", "0111001001", false),
      "00101111" -> ("0101111001", "1010001001", true),
      "00110000" -> ("0110111001", "1001001001", true),
      "00110001" -> ("1000111001", "1000111001", false),
      "00110010" -> ("0100111001", "0100111001", false),
      "00110011" -> ("1100101001", "1100101001", false),
      "00110100" -> ("0010111001", "0010111001", false),
      "00110101" -> ("1010101001", "1010101001", false),
      "00110110" -> ("0110101001", "0110101001", false),
      "00110111" -> ("1110101001", "0001011001", true),
      "00111000" -> ("1100111001", "0011001001", true),
      "00111001" -> ("1001101001", "1001101001", false),
      "00111010" -> ("0101101001", "0101101001", false),
      "00111011" -> ("1101101001", "0010011001", true),
      "00111100" -> ("0011101001", "0011101001", false),
      "00111101" -> ("1011101001", "0100011001", true),
      "00111110" -> ("0111101001", "1000011001", true),
      "00111111" -> ("1010111001", "0101001001", true),
      "01000000" -> ("1001110101", "0110000101", true),
      "01000001" -> ("0111010101", "1000100101", true),
      "01000010" -> ("1011010101", "0100100101", true),
      "01000011" -> ("1100010101", "1100010101", false),
      "01000100" -> ("1101010101", "0010100101", true),
      "01000101" -> ("1010010101", "1010010101", false),
      "01000110" -> ("0110010101", "0110010101", false),
      "01000111" -> ("1110000101", "0001110101", false),
      "01001000" -> ("1110010101", "0001100101", true),
      "01001001" -> ("1001010101", "1001010101", false),
      "01001010" -> ("0101010101", "0101010101", false),
      "01001011" -> ("1101000101", "1101000101", false),
      "01001100" -> ("0011010101", "0011010101", false),
      "01001101" -> ("1011000101", "1011000101", false),
      "01001110" -> ("0111000101", "0111000101", false),
      "01001111" -> ("0101110101", "1010000101", true),
      "01010000" -> ("0110110101", "1001000101", true),
      "01010001" -> ("1000110101", "1000110101", false),
      "01010010" -> ("0100110101", "0100110101", false),
      "01010011" -> ("1100100101", "1100100101", false),
      "01010100" -> ("0010110101", "0010110101", false),
      "01010101" -> ("1010100101", "1010100101", false),
      "01010110" -> ("0110100101", "0110100101", false),
      "01010111" -> ("1110100101", "0001010101", true),
      "01011000" -> ("1100110101", "0011000101", true),
      "01011001" -> ("1001100101", "1001100101", false),
      "01011010" -> ("0101100101", "0101100101", false),
      "01011011" -> ("1101100101", "0010010101", true),
      "01011100" -> ("0011100101", "0011100101", false),
      "01011101" -> ("1011100101", "0100010101", true),
      "01011110" -> ("0111100101", "1000010101", true),
      "01011111" -> ("1010110101", "0101000101", true),
      "01100000" -> ("1001110011", "0110001100", true),
      "01100001" -> ("0111010011", "1000101100", true),
      "01100010" -> ("1011010011", "0100101100", true),
      "01100011" -> ("1100011100", "1100010011", false),
      "01100100" -> ("1101010011", "0010101100", true),
      "01100101" -> ("1010011100", "1010010011", false),
      "01100110" -> ("0110011100", "0110010011", false),
      "01100111" -> ("1110001100", "0001110011", false),
      "01101000" -> ("1110010011", "0001101100", true),
      "01101001" -> ("1001011100", "1001010011", false),
      "01101010" -> ("0101011100", "0101010011", false),
      "01101011" -> ("1101001100", "1101000011", false),
      "01101100" -> ("0011011100", "0011010011", false),
      "01101101" -> ("1011001100", "1011000011", false),
      "01101110" -> ("0111001100", "0111000011", false),
      "01101111" -> ("0101110011", "1010001100", true),
      "01110000" -> ("0110110011", "1001001100", true),
      "01110001" -> ("1000111100", "1000110011", false),
      "01110010" -> ("0100111100", "0100110011", false),
      "01110011" -> ("1100101100", "1100100011", false),
      "01110100" -> ("0010111100", "0010110011", false),
      "01110101" -> ("1010101100", "1010100011", false),
      "01110110" -> ("0110101100", "0110100011", false),
      "01110111" -> ("1110100011", "0001011100", true),
      "01111000" -> ("1100110011", "0011001100", true),
      "01111001" -> ("1001101100", "1001100011", false),
      "01111010" -> ("0101101100", "0101100011", false),
      "01111011" -> ("1101100011", "0010011100", true),
      "01111100" -> ("0011101100", "0011100011", false),
      "01111101" -> ("1011100011", "0100011100", true),
      "01111110" -> ("0111100011", "1000011100", true),
      "01111111" -> ("1010110011", "0101001100", true),
      "10000000" -> ("1001110010", "0110001101", false),
      "10000001" -> ("0111010010", "1000101101", false),
      "10000010" -> ("1011010010", "0100101101", false),
      "10000011" -> ("1100011101", "1100010010", true),
      "10000100" -> ("1101010010", "0010101101", false),
      "10000101" -> ("1010011101", "1010010010", true),
      "10000110" -> ("0110011101", "0110010010", true),
      "10000111" -> ("1110001101", "0001110010", true),
      "10001000" -> ("1110010010", "0001101101", false),
      "10001001" -> ("1001011101", "1001010010", true),
      "10001010" -> ("0101011101", "0101010010", true),
      "10001011" -> ("1101001101", "1101000010", true),
      "10001100" -> ("0011011101", "0011010010", true),
      "10001101" -> ("1011001101", "1011000010", true),
      "10001110" -> ("0111001101", "0111000010", true),
      "10001111" -> ("0101110010", "1010001101", false),
      "10010000" -> ("0110110010", "1001001101", false),
      "10010001" -> ("1000111101", "1000110010", true),
      "10010010" -> ("0100111101", "0100110010", true),
      "10010011" -> ("1100101101", "1100100010", true),
      "10010100" -> ("0010111101", "0010110010", true),
      "10010101" -> ("1010101101", "1010100010", true),
      "10010110" -> ("0110101101", "0110100010", true),
      "10010111" -> ("1110100010", "0001011101", false),
      "10011000" -> ("1100110010", "0011001101", false),
      "10011001" -> ("1001101101", "1001100010", true),
      "10011010" -> ("0101101101", "0101100010", true),
      "10011011" -> ("1101100010", "0010011101", false),
      "10011100" -> ("0011101101", "0011100010", true),
      "10011101" -> ("1011100010", "0100011101", false),
      "10011110" -> ("0111100010", "1000011101", false),
      "10011111" -> ("1010110010", "0101001101", false),
      "10100000" -> ("1001111010", "0110001010", true),
      "10100001" -> ("0111011010", "1000101010", true),
      "10100010" -> ("1011011010", "0100101010", true),
      "10100011" -> ("1100011010", "1100011010", false),
      "10100100" -> ("1101011010", "0010101010", true),
      "10100101" -> ("1010011010", "1010011010", false),
      "10100110" -> ("0110011010", "0110011010", false),
      "10100111" -> ("1110001010", "0001111010", false),
      "10101000" -> ("1110011010", "0001101010", true),
      "10101001" -> ("1001011010", "1001011010", false),
      "10101010" -> ("0101011010", "0101011010", false),
      "10101011" -> ("1101001010", "1101001010", false),
      "10101100" -> ("0011011010", "0011011010", false),
      "10101101" -> ("1011001010", "1011001010", false),
      "10101110" -> ("0111001010", "0111001010", false),
      "10101111" -> ("0101111010", "1010001010", true),
      "10110000" -> ("0110111010", "1001001010", true),
      "10110001" -> ("1000111010", "1000111010", false),
      "10110010" -> ("0100111010", "0100111010", false),
      "10110011" -> ("1100101010", "1100101010", false),
      "10110100" -> ("0010111010", "0010111010", false),
      "10110101" -> ("1010101010", "1010101010", false),
      "10110110" -> ("0110101010", "0110101010", false),
      "10110111" -> ("1110101010", "0001011010", true),
      "10111000" -> ("1100111010", "0011001010", true),
      "10111001" -> ("1001101010", "1001101010", false),
      "10111010" -> ("0101101010", "0101101010", false),
      "10111011" -> ("1101101010", "0010011010", true),
      "10111100" -> ("0011101010", "0011101010", false),
      "10111101" -> ("1011101010", "0100011010", true),
      "10111110" -> ("0111101010", "1000011010", true),
      "10111111" -> ("1010111010", "0101001010", true),
      "11000000" -> ("1001110110", "0110000110", true),
      "11000001" -> ("0111010110", "1000100110", true),
      "11000010" -> ("1011010110", "0100100110", true),
      "11000011" -> ("1100010110", "1100010110", false),
      "11000100" -> ("1101010110", "0010100110", true),
      "11000101" -> ("1010010110", "1010010110", false),
      "11000110" -> ("0110010110", "0110010110", false),
      "11000111" -> ("1110000110", "0001110110", false),
      "11001000" -> ("1110010110", "0001100110", true),
      "11001001" -> ("1001010110", "1001010110", false),
      "11001010" -> ("0101010110", "0101010110", false),
      "11001011" -> ("1101000110", "1101000110", false),
      "11001100" -> ("0011010110", "0011010110", false),
      "11001101" -> ("1011000110", "1011000110", false),
      "11001110" -> ("0111000110", "0111000110", false),
      "11001111" -> ("0101110110", "1010000110", true),
      "11010000" -> ("0110110110", "1001000110", true),
      "11010001" -> ("1000110110", "1000110110", false),
      "11010010" -> ("0100110110", "0100110110", false),
      "11010011" -> ("1100100110", "1100100110", false),
      "11010100" -> ("0010110110", "0010110110", false),
      "11010101" -> ("1010100110", "1010100110", false),
      "11010110" -> ("0110100110", "0110100110", false),
      "11010111" -> ("1110100110", "0001010110", true),
      "11011000" -> ("1100110110", "0011000110", true),
      "11011001" -> ("1001100110", "1001100110", false),
      "11011010" -> ("0101100110", "0101100110", false),
      "11011011" -> ("1101100110", "0010010110", true),
      "11011100" -> ("0011100110", "0011100110", false),
      "11011101" -> ("1011100110", "0100010110", true),
      "11011110" -> ("0111100110", "1000010110", true),
      "11011111" -> ("1010110110", "0101000110", true),
      "11100000" -> ("1001110001", "0110001110", false),
      "11100001" -> ("0111010001", "1000101110", false),
      "11100010" -> ("1011010001", "0100101110", false),
      "11100011" -> ("1100011110", "1100010001", true),
      "11100100" -> ("1101010001", "0010101110", false),
      "11100101" -> ("1010011110", "1010010001", true),
      "11100110" -> ("0110011110", "0110010001", true),
      "11100111" -> ("1110001110", "0001110001", true),
      "11101000" -> ("1110010001", "0001101110", false),
      "11101001" -> ("1001011110", "1001010001", true),
      "11101010" -> ("0101011110", "0101010001", true),
      "11101011" -> ("1101001110", "1101001000", true),
      "11101100" -> ("0011011110", "0011010001", true),
      "11101101" -> ("1011001110", "1011001000", true),
      "11101110" -> ("0111001110", "0111001000", true),
      "11101111" -> ("0101110001", "1010001110", false),
      "11110000" -> ("0110110001", "1001001110", false),
      "11110001" -> ("1000110111", "1000110001", true),
      "11110010" -> ("0100110111", "0100110001", true),
      "11110011" -> ("1100101110", "1100100001", true),
      "11110100" -> ("0010110111", "0010110001", true),
      "11110101" -> ("1010101110", "1010100001", true),
      "11110110" -> ("0110101110", "0110100001", true),
      "11110111" -> ("1110100001", "0001011110", false),
      "11111000" -> ("1100110001", "0011001110", false),
      "11111001" -> ("1001101110", "1001100001", true),
      "11111010" -> ("0101101110", "0101100001", true),
      "11111011" -> ("1101100001", "0010011110", false),
      "11111100" -> ("0011101110", "0011100001", true),
      "11111101" -> ("1011100001", "0100011110", false),
      "11111110" -> ("0111100001", "1000011110", false),
      "11111111" -> ("1010110001", "0101001110", false)
  )

  test("encode") {
    val compiled = SimConfig.withWave.compile {
      val dut = Encoding8b10b.Encoder()
      dut
    }
    compiled.doSim("Verify Pipeline and RD transition") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.data #= BigInt(0)
      dut.clockDomain.waitSampling(2)

      encodeTestCaseDWord(dut, "00000010", "1011010100")
      encodeTestCaseDWord(dut, "00000001", "0111010100")
      encodeTestCaseDWord(dut, "00000000", "1001110100")
      encodeTestCaseDWord(dut, "00000001", "0111010100")
      encodeTestCaseDWord(dut, "00000010", "1011010100")
      encodeTestCaseDWord(dut, "00000011", "1100011011")
    }

    compiled.doSim("All valid D-Words") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.data #= BigInt(0)
      dut.clockDomain.waitSampling(2)

      var rd = false
      for ((kWord, data) <- dWords) {
        val encoded = if (!rd) data._1 else data._2
        encodeTestCaseDWord(dut, kWord, encoded)
        if (data._3) {
          println(s"Switch RD from ${rd} to ${!rd}")
          rd = !rd
        }
      }
    }

    compiled.doSim("K-Word error") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.data #= BigInt(0)
      dut.clockDomain.waitSampling(2)

      encodeTestCaseKWord(dut, "00000000", "1001110100", true)
    }

    compiled.doSim("All valid K-Words") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.data #= BigInt(0)
      dut.clockDomain.waitSampling(2)

      var rd = false
      for ((kWord, data) <- kWords) {
        val encoded = if (!rd) data._1 else data._2
        encodeTestCaseKWord(dut, kWord, encoded)
        if (data._3) {
          println(s"Switch RD from ${rd} to ${!rd}")
          rd = !rd
        }
      }
    }
  }
  test("decode") {
    val compiled = SimConfig.withWave.compile {
      val dut = Encoding8b10b.Decoder()
      dut
    }
    compiled.doSim("Verify Pipeline and RD transition") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.encoded #= BigInt("1011010100", 2)
      dut.clockDomain.waitSampling(2)

      decodeTestCaseDWord(dut, "00000010", "1011010100")
      decodeTestCaseDWord(dut, "00000001", "0111010100")
      decodeTestCaseDWord(dut, "00000000", "1001110100")
      decodeTestCaseDWord(dut, "00000001", "0111010100")
      decodeTestCaseDWord(dut, "00000010", "1011010100")
      decodeTestCaseDWord(dut, "00000011", "1100011011")
    }

    compiled.doSim("All valid D-Words") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.encoded #= BigInt("1011010100", 2)
      dut.clockDomain.waitSampling(2)

      var rd = false
      for ((kWord, data) <- dWords) {
        val encoded = if (!rd) data._1 else data._2
        decodeTestCaseDWord(dut, kWord, encoded)
        if (data._3) {
          println(s"Switch RD from ${rd} to ${!rd}")
          rd = !rd
        }
      }
    }

    compiled.doSim("All valid K-Words") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.encoded #= BigInt("1011010100", 2)
      dut.clockDomain.waitSampling(2)

      var rd = false
      for ((kWord, data) <- kWords) {
        val encoded = if (!rd) data._1 else data._2
        decodeTestCaseKWord(dut, kWord, encoded)
        if (data._3) {
          println(s"Switch RD from ${rd} to ${!rd}")
          rd = !rd
        }
      }
    }

    compiled.doSim("Disparity Error") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.encoded #= BigInt("1011010100", 2)
      dut.clockDomain.waitSampling(2)

      decodeTestCaseError(dut, "00000011", "1100011011", false)
      decodeTestCaseError(dut, "00000011", "1100011011", true)
    }

    compiled.doSim("Code Error") { dut =>
      dut.clockDomain.forkStimulus(period = 10)
      dut.io.encoded #= BigInt("1011010100", 2)
      dut.clockDomain.waitSampling(2)

      decodeTestCaseError(dut, "00000011", "1001110000", true)
    }


  }
}
